{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "som.ipynb",
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "uCQrVzZSoFi-"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Define the Winner Take All units\n",
        "class WTU(object):\n",
        "  def __init__(self, m, n, dim, num_iterations, eta = 0.5, sigma = None):\n",
        "    self._m = m\n",
        "    self._n = n\n",
        "    self._neighbourhood = []\n",
        "    self._topography = []\n",
        "    self._num_iterations = int(num_iterations) \n",
        "    self._learned = False\n",
        "    self.dim = dim\n",
        "    \n",
        "    self.eta = float(eta)\n",
        "        \n",
        "    if sigma is None:\n",
        "        sigma = max(m,n)/2.0    # Constant radius\n",
        "    else:\n",
        "        sigma = float(sigma)\n",
        "    self.sigma = sigma\n",
        "    \n",
        "        \n",
        "    print('Network created with dimensions',m,n)\n",
        "        \n",
        "    # Weight Matrix and the topography of neurons\n",
        "    self._W = tf.random.normal([m*n, dim], seed = 0)\n",
        "    self._topography = np.array(list(self._neuron_location(m, n)))\n",
        "          \n",
        "  def training(self,x, i):\n",
        "    m = self._m\n",
        "    n= self._n \n",
        "    \n",
        "    # Finding the Winner and its location\n",
        "    d = tf.sqrt(tf.reduce_sum(tf.pow(self._W - tf.stack([x for i in range(m*n)]),2),1))\n",
        "    self.WTU_idx = tf.argmin(d,0)\n",
        "    \n",
        "    slice_start = tf.pad(tf.reshape(self.WTU_idx, [1]),np.array([[0,1]]))\n",
        "    self.WTU_loc = tf.reshape(tf.slice(self._topography, slice_start,[1,2]), [2])\n",
        "    \n",
        "    \n",
        "    # Change learning rate and radius as a function of iterations\n",
        "    learning_rate = 1 - i/self._num_iterations\n",
        "    _eta_new = self.eta * learning_rate\n",
        "    _sigma_new = self.sigma * learning_rate\n",
        "    \n",
        "    \n",
        "    # Calculating Neighbourhood function\n",
        "    distance_square = tf.reduce_sum(tf.pow(tf.subtract(\n",
        "        self._topography, tf.stack([self.WTU_loc for i in range(m * n)])), 2), 1)\n",
        "    neighbourhood_func = tf.exp(tf.negative(tf.math.divide(tf.cast(\n",
        "        distance_square, \"float32\"), tf.pow(_sigma_new, 2))))\n",
        "    \n",
        "    # multiply learning rate with neighbourhood func\n",
        "    eta_into_Gamma = tf.multiply(_eta_new, neighbourhood_func)\n",
        "    \n",
        "    # Shape it so that it can be multiplied to calculate dW\n",
        "    weight_multiplier = tf.stack([tf.tile(tf.slice(\n",
        "        eta_into_Gamma, np.array([i]), np.array([1])), [self.dim])\n",
        "        for i in range(m * n)])\n",
        "    delta_W = tf.multiply(weight_multiplier,\n",
        "        tf.subtract(tf.stack([x for i in range(m * n)]),self._W))\n",
        "    new_W = self._W + delta_W\n",
        "    self._W = new_W\n",
        "            \n",
        "           \n",
        "  def fit(self, X):\n",
        "    for i in range(self._num_iterations):\n",
        "        for x in X:\n",
        "              self.training(x,i)\n",
        "        \n",
        "    \n",
        "    \n",
        "    # Store a centroid grid for easy retrieval\n",
        "    centroid_grid = [[] for i in range(self._m)]\n",
        "    self._Wts = list(self._W)\n",
        "    self._locations = list(self._topography)\n",
        "    for i, loc in enumerate(self._locations):\n",
        "        centroid_grid[loc[0]].append(self._Wts[i])\n",
        "    self._centroid_grid = centroid_grid\n",
        "\n",
        "    self._learned = True\n",
        "    \n",
        "  def winner(self, x):\n",
        "    idx = self.WTU_idx,self.WTU_loc\n",
        "    return idx\n",
        "             \n",
        "  def _neuron_location(self,m,n):\n",
        "        \n",
        "    for i in range(m):\n",
        "        for j in range(n):\n",
        "            yield np.array([i,j])\n",
        "                \n",
        "                \n",
        "  def get_centroids(self):\n",
        "       \n",
        "    if not self._learned:\n",
        "        raise ValueError(\"SOM not trained yet\")\n",
        "    return self._centroid_grid\n",
        "\n",
        "  def map_vects(self, X):\n",
        "       \n",
        "\n",
        "    if not self._learned:\n",
        "        raise ValueError(\"SOM not trained yet\")\n",
        "\n",
        "    to_return = []\n",
        "    for vect in X:\n",
        "        min_index = min([i for i in range(len(self._Wts))],\n",
        "                        key=lambda x: np.linalg.norm(vect -\n",
        "                                                      self._Wts[x]))\n",
        "        to_return.append(self._locations[min_index])\n",
        "\n",
        "    return to_return"
      ],
      "metadata": {
        "id": "LfVbZ2RfsOcD"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "def normalize(df):\n",
        "    result = df.copy()\n",
        "    for feature_name in df.columns:\n",
        "        max_value = df[feature_name].max()\n",
        "        min_value = df[feature_name].min()\n",
        "        result[feature_name] = (df[feature_name] - min_value) / (max_value - min_value)\n",
        "    return result.astype(np.float32)\n",
        "\n"
      ],
      "metadata": {
        "id": "cS-woLe_FY3_"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "!wget https://raw.githubusercontent.com/PacktPublishing/Deep-Learning-with-TensorFlow-2-and-Keras/master/Chapter%2010/colors.csv"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "NzSNVbDQhP_i",
        "outputId": "573b1484-f064-41ae-afad-a4f8a0d83b8e"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "--2022-02-04 23:20:10--  https://raw.githubusercontent.com/PacktPublishing/Deep-Learning-with-TensorFlow-2-and-Keras/master/Chapter%2010/colors.csv\n",
            "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.110.133, 185.199.111.133, ...\n",
            "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.\n",
            "HTTP request sent, awaiting response... 200 OK\n",
            "Length: 269 [text/plain]\n",
            "Saving to: ‘colors.csv’\n",
            "\n",
            "\rcolors.csv            0%[                    ]       0  --.-KB/s               \rcolors.csv          100%[===================>]     269  --.-KB/s    in 0s      \n",
            "\n",
            "2022-02-04 23:20:10 (12.1 MB/s) - ‘colors.csv’ saved [269/269]\n",
            "\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "## Reading input data from file\n",
        "import pandas as pd\n",
        "\n",
        "df = pd.read_csv('colors.csv')  # The last column of data file is a label\n",
        "data = normalize(df[['R', 'G', 'B']]).values\n",
        "name = df['Color-Name'].values\n",
        "n_dim = len(df.columns) - 1"
      ],
      "metadata": {
        "id": "Q--S0j6pheFC"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Data for Training\n",
        "colors = data\n",
        "color_names = name"
      ],
      "metadata": {
        "id": "AM7-1EvLFdWy"
      },
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "%%time\n",
        "som = WTU(30, 30, n_dim, 400, sigma=10.0)\n",
        "som.fit(colors)"
      ],
      "metadata": {
        "id": "jm2pmkIiFgLQ",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "5b9deb06-9916-4618-8de0-9bfeafad38a5"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Network created with dimensions 30 30\n",
            "CPU times: user 1h 13min 56s, sys: 11min 38s, total: 1h 25min 35s\n",
            "Wall time: 1h 5min 6s\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Get output grid\n",
        "image_grid = som.get_centroids()\n",
        "\n",
        "# Map colours to their closest neurons\n",
        "mapped = som.map_vects(colors)\n",
        "\n",
        "# Plot\n",
        "plt.imshow(image_grid)\n",
        "plt.title('Color Grid SOM')\n",
        "for i, m in enumerate(mapped):\n",
        "    plt.text(m[1], m[0], color_names[i], ha='center', va='center',\n",
        "             bbox=dict(facecolor='white', alpha=0.5, lw=0))"
      ],
      "metadata": {
        "id": "WYjBYz-IFlhD",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 281
        },
        "outputId": "4665102e-be12-4ac0-8a00-30405085c776"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "idx, loc = som.winner([0.5, 0.5, 0.5])\n",
        "print(idx, loc)\n"
      ],
      "metadata": {
        "id": "ot2hccceFqiR",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "6137f955-508d-4f2c-f34c-c30c7ed8cc53"
      },
      "execution_count": null,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "tf.Tensor(501, shape=(), dtype=int64) tf.Tensor([16 21], shape=(2,), dtype=int64)\n"
          ]
        }
      ]
    }
  ]
}